WithdrawWidget.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. "use client";
  2. import { Wallet } from "@/api/user";
  3. import { ChannelType, getWithDrawApi, WithDrawType } from "@/api/withdraw";
  4. import { clearWallet } from "@/app/[locale]/(navbar)/withdraw/actions";
  5. import Box from "@/components/Box";
  6. import ButtonOwn from "@/components/ButtonOwn";
  7. import Empty from "@/components/Empty";
  8. import TipsModal, { ModalProps } from "@/components/TipsModal";
  9. import { useUserInfoStore } from "@/stores/useUserInfoStore";
  10. import { useWalletStore } from "@/stores/useWalletStore";
  11. import { isEmail } from "@/utils";
  12. import { ActionSheet, Form, Input, Toast } from "antd-mobile";
  13. import type { Action } from "antd-mobile/es/components/action-sheet";
  14. import { FormInstance } from "antd-mobile/es/components/form";
  15. import { useTranslations } from "next-intl";
  16. import Link from "next/link";
  17. import { FC, Fragment, useRef, useState } from "react";
  18. import "./page.scss";
  19. interface Props {
  20. channels: WithDrawType[];
  21. wallet: Wallet;
  22. }
  23. export enum ChannelEnum {
  24. CPF = 1,
  25. Email,
  26. Phone,
  27. CNPJ,
  28. }
  29. type FieldValueType = {
  30. account_no: string;
  31. channel_id: (typeof ChannelEnum)[keyof typeof ChannelEnum];
  32. type: number;
  33. };
  34. interface MobileFieldProps {
  35. value?: FieldValueType;
  36. onChange?: (value: FieldValueType) => void;
  37. actions: Array<ChannelType & { text: string; key: number }>;
  38. }
  39. const MobileField: FC<MobileFieldProps> = (props) => {
  40. const {
  41. actions,
  42. value = { account_no: "", channel_id: actions[0].id, type: actions[0].type },
  43. onChange,
  44. } = props;
  45. let [visible, setVisible] = useState(false);
  46. const t = useTranslations("WithdrawPage");
  47. let [prefix, setPrefix] = useState<ChannelType & { text: string; key: number }>(actions[0]);
  48. let [placeholder, setPlaceholder] = useState("000.000.000-00");
  49. const onAction = (item: any) => {
  50. setPrefix(item);
  51. onRealValueChange("");
  52. if (item.type === ChannelEnum.CPF) {
  53. setPlaceholder("000.000.000-00");
  54. return;
  55. }
  56. if (item.type === ChannelEnum.Phone) {
  57. setPlaceholder("11 dígitos");
  58. return;
  59. }
  60. if (item.type === ChannelEnum.Email) {
  61. setPlaceholder("Email");
  62. return;
  63. }
  64. };
  65. const onRealValueChange = (value: string) => {
  66. if (onChange) {
  67. onChange({ account_no: value, channel_id: prefix.id, type: prefix.type });
  68. }
  69. };
  70. return (
  71. <>
  72. <div className="flex">
  73. <div onClick={() => setVisible(true)} className={"mr-[0.1rem] flex flex-shrink-0"}>
  74. <span className={"mr-[0.1rem]"}>{prefix.text}</span>
  75. <span className="iconfont icon-xialaxuanze"></span>
  76. </div>
  77. <Input
  78. name="secretKey"
  79. value={value.account_no}
  80. placeholder={placeholder}
  81. onChange={onRealValueChange}
  82. />
  83. </div>
  84. <ActionSheet
  85. extra=""
  86. cancelText={t("Cancelar")}
  87. visible={visible}
  88. actions={actions}
  89. style={{ background: "#fff" }}
  90. closeOnAction={true}
  91. getContainer={null}
  92. onAction={onAction}
  93. onClose={() => setVisible(false)}
  94. />
  95. </>
  96. );
  97. };
  98. const WithdrawWidget: FC<Props> = (props) => {
  99. const t = useTranslations();
  100. const { channels, wallet } = props;
  101. const score = useWalletStore((state) => state.score)!;
  102. const withdrawRef = useRef<ModalProps>(null);
  103. const formRef = useRef<FormInstance>(null);
  104. const [activeWallet, setActiveWallet] = useState<WithDrawType>(channels[0]);
  105. const walletAction =
  106. activeWallet &&
  107. activeWallet.channels?.map((item) => ({
  108. text: ChannelEnum[item.type],
  109. key: item.id,
  110. ...item,
  111. }));
  112. const defaultActions: Array<Action & { id: number }> = [
  113. { text: "CPF", key: "CPF", id: ChannelEnum.CPF },
  114. { text: t("WithdrawPage.Número"), key: "Celular", id: ChannelEnum.Phone },
  115. { text: "EMAIL", key: "EMAIL", id: ChannelEnum.Email },
  116. ];
  117. const userInfo = useUserInfoStore((state) => state.userInfo);
  118. const initParams = {
  119. channel: "",
  120. amount: "",
  121. passport: userInfo.passport,
  122. user_name: userInfo.user_name,
  123. };
  124. const AmountValidator = (rules: any, value: string) => {
  125. const num = +value;
  126. if (num > activeWallet.max_amount) {
  127. return Promise.reject(
  128. new Error(t("WithdrawPage.amountReg", { max: activeWallet.max_amount }))
  129. );
  130. }
  131. if (score && num > score) {
  132. return Promise.reject(new Error(t("WithdrawPage.amountMaxReg")));
  133. }
  134. return Promise.resolve();
  135. };
  136. const ChannelValidator = (rules: any, value: FieldValueType) => {
  137. if (!value.account_no) return Promise.reject(new Error(t("WithdrawPage.channel")));
  138. if (value.type === ChannelEnum.CPF) {
  139. return value.account_no.length !== 11
  140. ? Promise.reject(new Error(t("WithdrawPage.cpfReg")))
  141. : Promise.resolve();
  142. }
  143. if (value.type === ChannelEnum.Email) {
  144. return isEmail(value.account_no)
  145. ? Promise.resolve()
  146. : Promise.reject(new Error(t("WithdrawPage.EmailReg")));
  147. }
  148. if (value.type === ChannelEnum.Phone) {
  149. return value.account_no.length < 11
  150. ? Promise.reject(new Error(t("WithdrawPage.phoneReg")))
  151. : Promise.resolve();
  152. }
  153. return Promise.resolve();
  154. };
  155. const onFinish = async (value: any) => {
  156. const params = { ...value, ...value.channel, amount: +value.amount };
  157. getWithDrawApi(params)
  158. .then((res) => {
  159. if (res.code === 200) {
  160. Toast.show(t("code.200"));
  161. }
  162. })
  163. .catch((error) => {
  164. Toast.show(t(`code.${error.data.code}`));
  165. });
  166. await clearWallet();
  167. };
  168. if (!activeWallet) return <Empty />;
  169. return (
  170. <>
  171. <Box className={"custom-form"}>
  172. <div className="withdraw-box">
  173. <div className="img-box"></div>
  174. <div className={"mb-[0.1rem] flex flex-wrap gap-[0.0347rem]"}>
  175. {channels.map((item) => {
  176. return (
  177. <Fragment key={item.id}>
  178. <p
  179. className={`btn-box ${activeWallet.id === item.id ? "active" : ""}`}
  180. onClick={() => {
  181. formRef.current?.resetFields();
  182. setActiveWallet(item);
  183. }}
  184. >
  185. {item.name}
  186. </p>
  187. </Fragment>
  188. );
  189. })}
  190. </div>
  191. <h1>{t("WithdrawPage.Certifique")}</h1>
  192. <p className={"text-[0.1rem] text-[#3bc117]"}>{t("WithdrawPage.keyTips")}</p>
  193. {/* form */}
  194. <Form
  195. style={{
  196. "--border-bottom": "none",
  197. "--border-top": "none",
  198. "--border-inner": "none",
  199. }}
  200. ref={formRef}
  201. onFinish={onFinish}
  202. initialValues={initParams}
  203. disabled={!activeWallet.channels}
  204. hasFeedback={!!activeWallet.channels}
  205. className={"mt-[0.1rem]"}
  206. footer={<ButtonOwn active>{t("WithdrawPage.Saque")}</ButtonOwn>}
  207. >
  208. <Form.Item
  209. name="user_name"
  210. label=""
  211. rules={[{ required: true, message: t("WithdrawPage.usernameReg") }]}
  212. >
  213. <Input placeholder={t("WithdrawPage.username")} />
  214. </Form.Item>
  215. <Form.Item
  216. name="passport"
  217. label=""
  218. rules={[
  219. {
  220. required: true,
  221. message: t("WithdrawPage.cpfReg"),
  222. min: 6,
  223. max: 20,
  224. },
  225. ]}
  226. >
  227. <Input
  228. placeholder={t("WithdrawPage.cpf")}
  229. maxLength={20}
  230. type={"text"}
  231. />
  232. </Form.Item>
  233. <p className={"my-[0.1rem]"}>{t("WithdrawPage.Tipo")}</p>
  234. {activeWallet.channels?.length && (
  235. <Form.Item
  236. name="channel"
  237. label=""
  238. rules={[{ validator: ChannelValidator }]}
  239. >
  240. <MobileField actions={walletAction || []} />
  241. </Form.Item>
  242. )}
  243. <h1>{t("WithdrawPage.Vincule")}</h1>
  244. <p className={"my-[0.1rem]"}>{t("WithdrawPage.Montante")} (BRL):</p>
  245. <Form.Item
  246. name="amount"
  247. label=""
  248. rules={[
  249. { required: true, message: t("WithdrawPage.amount") },
  250. {
  251. type: "number",
  252. validator: AmountValidator,
  253. },
  254. ]}
  255. >
  256. <Input
  257. placeholder={`Mín. ${activeWallet.min_amount || 0}`}
  258. type={"number"}
  259. min={activeWallet.min_amount}
  260. />
  261. </Form.Item>
  262. <ul className="ul-box">
  263. <li>
  264. {t("WithdrawPage.SaqueDisponivel")}{" "}
  265. <span className="tip">{wallet.score} BRL</span>
  266. </li>
  267. <li>
  268. {t("WithdrawPage.Valor")} <span className="tip">0 BRL</span>{" "}
  269. <span
  270. className="iconfont icon-iconhelp"
  271. onClick={() => withdrawRef.current?.onOpen()}
  272. ></span>
  273. </li>
  274. <li>
  275. {t("WithdrawPage.Para")},{" "}
  276. <Link href="/" className="toHome router-link-active" replace>
  277. {t("WithdrawPage.Aposte")}
  278. </Link>
  279. </li>
  280. </ul>
  281. </Form>
  282. </div>
  283. </Box>
  284. <TipsModal title={"提现弹窗"} ref={withdrawRef}>
  285. <ul>
  286. <li>1</li>
  287. <li>2</li>
  288. <li>3</li>
  289. <li>4</li>
  290. </ul>
  291. </TipsModal>
  292. </>
  293. );
  294. };
  295. export default WithdrawWidget;